import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Info: 代码来源：https://www.cnblogs.com/enjoyitlife/p/12093960.html
 * @Author: 唐小尊
 * @Date: 2020/3/2  15:04
 */
public class ThreadCommunication {
    public static void main(String[] args) {
//        notifyThreadWithParkUnpark();
//        notifyThreadWithSynchronized();
        notifyThreadWithCountDownLatch();
    }

    /**
     * TODO 利用Park/Unpak实现线程通信
     * park与unpark可以看做一个令牌，park就是等待令牌，unpark就是颁发一个令牌，另外需要注意的是park与unpark的调用次数不用一一对应，而且假如在同步代码块中调用park方法，线程会进入阻塞状态，但是不会释放已经占用的锁。
     * 本例使用park使线程B进入阻塞等待状态，在线程A调用unpark并传入线程B的名称使线程B可以继续运行。
     * 操作相当于允许第二个线程工作
     */
    private static void notifyThreadWithParkUnpark() {
        Thread thb = new Thread(() -> {
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "启动了");
        }, "线程B");
        Thread tha = new Thread(() -> {
            for (int i = 1; i < 11; i++) {
                System.out.println(Thread.currentThread().getName() + i);
                if (i == 5) {
                    LockSupport.unpark(thb);
                    //延时0.5秒便于看效果
                    try {
                        Thread.sleep(500L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "线程A");
        thb.start();
        tha.start();
    }


    /**
     * TODO 使用Volatile关键字实现线程通信
     * volatile表示的禁用CPU缓存，用volatile修饰的变量，会强制从主内存中读取变量的值。java内存模型中关于volatile也是有说明的，volatile只能保证可见性，但不能保证原子性。
     * 本例通过在volatile来修饰一个标志位，线程C修改了该标志位，然后线程D就可以“看到”标志位的修改，从而实现互相通信。
     */
    private static volatile boolean flag = false;

    private static void notifyThreadWithVolatile() {
        Thread thc = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                if (i == 5) {
                    flag = true;
                    try {
                        Thread.sleep(500L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + i);
            }
        }, "线程C");
        Thread thd = new Thread(() -> {
            while (true) {
                // 防止伪唤醒 所以使用了while
                while (flag) {
                    System.out.println(Thread.currentThread().getName() + "收到通知");
                    break;
                }
            }
        }, "线程D");
        thd.start();
        try {
            Thread.sleep(1000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        thc.start();
    }

    /**
     * TODO 使用Synchronized 集合wait notify实现线程间通信
     * synchronized修饰同步代码块，而wait notify notify必须是在synchronized修饰代码块中使用，否则会抛出监视器异常。
     * 本实例定义一个对象锁，而线程F首先获取到互斥锁，在执行wait()方法时，释放已经持有的互斥锁，进入等待队列。而线程E执行获取到互斥锁开始执行，当1==5时，调用notify方法，就会通知lock的等待队列，然后线程E会继续执行，由于线程F此时还是获取不到互斥锁（因为被线程E占用），所以会在线程E执行完毕后，才能获取到执行权。
     */
    private static final Object lock = new Object();
    private static void notifyThreadWithSynchronized() {
        Thread the = new Thread(()->{
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                    if (i == 5) {
                        lock.notify();
                    }
                }
            }
        },"线程E");
        Thread thf = new Thread(()->{
            while (true) {
                synchronized (lock) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "启动了");
                }
            }
        },"线程F");
        thf.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        the.start();
    }

    /**
     * TODO 利用CountDonwLatch实现线程间通信
     * 本示例中使用了CountDownLatch倒计时器，利用了倒计时器的阻塞特性来实现等待。具体就是声明一个计数器为1的倒计时器，线程G调用await()方法进入等待，直到计数器为0的时候才能够进入执行，而线程H在i==5会将计数器减一，使其为0，此时线程G就会继续执行了。
     */
    private static void notifyThreadWithCountDownLatch(){
        CountDownLatch cdl = new CountDownLatch(1);
        Thread thg = new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"等待countDown为0");
                cdl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"启动了");
        },"线程G");
        thg.start();
        Thread thh = new Thread(()->{
            for (int i = 1; i < 11; i++) {
                System.out.println(Thread.currentThread().getName()+i);
                if(i==5){
                    cdl.countDown();
                }
            }
        },"线程H");
        System.out.println(thg.getThreadGroup());
        thh.start();
    }

    /**
     * TODO 利用Condition中的await和signal来实现
     * 本示例是结合ReentrantLock和Condition来进行控制线程间的执行顺序，Condition的await()和signal()，他们的语义和wait notify是一样的。区别是在synchronized代码块里调用wait notify。通过示例可以看到这中方法实现会不断的加锁与解锁，所以看起来稍微复杂些。
     */
    private void notifyThreadWithCondition(){
        Lock rtl=new ReentrantLock();
        Condition condition = rtl.newCondition();
        Thread thi = new Thread(()->{
            while (true){
                rtl.lock();
                try {
                    condition.await();
                    System.out.println(Thread.currentThread().getName()+"启动了");
                    break;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    rtl.unlock();
                }
            }
        },"线程I");
        Thread thj = new Thread(()->{
            rtl.lock();
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                    if(i==5){
                        condition.signal();
                    }
                }
            } finally {
                rtl.unlock();
            }
        },"线程J");
        thi.start();
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thj.start();
    }

}